#include "AReVi/arSystem.h"
#include "AReVi/arObject.h"
#include "AReVi/Lib3D/viewer3D.h"
#include "AReVi/Lib3D/simpleInteractor.h"
#include "AReVi/Lib3D/light3D.h"
#include "AReVi/Lib3D/urlTexture.h"
#include "AReVi/Shapes/shader.h"
#include "AReVi/Shapes/surface3D.h"
#include "AReVi/Lib3D/gl_priv.h"
using namespace AReVi;

#include "hLib/hLib.h"
using namespace hLib;

/**********************************************
 * Mon shader capable de passer des attributs *
 **********************************************/
/*
class CustomShaderProgram : public ShaderProgram {
public:
	AR_CLASS(CustomShaderProgram)
	AR_CONSTRUCTOR(CustomShaderProgram)

protected:
	void _render(const ShapePart3D *, const Object3D *, const Renderer3D *) const;
	void _renderWithoutAppearance(const ShapePart3D *, const Object3D *, const Renderer3D *) const;
};

AR_CLASS_DEF(CustomShaderProgram, ShaderProgram)

CustomShaderProgram::CustomShaderProgram(ArCW & arCW) : ShaderProgram(arCW) {
}

CustomShaderProgram::~CustomShaderProgram() {
}

void CustomShaderProgram::_render(const ShapePart3D * part, const Object3D *, const Renderer3D *) const {
	if(_linked) {
		GL::glUseProgramObject(_handle);
		// On passe le paramètre
		GLint id = GL::glGetUniformLocation(getGLHandle(), "coeff");
		GL::glUniform1f(id, fmod(ArSystem::getTime()/3000.0, 1.0));

		_renderPart(part);
		GL::glUseProgramObject(0);
	}
	else {
		_renderPart(part);
	}
}

void CustomShaderProgram::_renderWithoutAppearance(const ShapePart3D * part, const Object3D *, const Renderer3D *) const {
	if(_linked) {
		GL::glUseProgramObject(_handle);
		_renderPartWithoutAppearance(part);
		GL::glUseProgramObject(0);
	}
	else {
		_renderPartWithoutAppearance(part);
	}
}
*/


/*****************************************
 * Ma classe permettant d'animer l'objet *
 *****************************************/

class Animator : public ArObject {
public:
	AR_CLASS(Animator)
	AR_CONSTRUCTOR_2(Animator, ArRef<Body>, body, ArRef<Animation>, animation)
	
protected:
	ArRef<Body> _body;
	ArRef<Animation> _animation;

	ArRef<Activity> _act;
	bool _animate(ArRef<Activity> act, double dt);
	double _time;
};

AR_CLASS_NOVOID_DEF(Animator, ArObject)

Animator::Animator(ArCW & arCW, ArRef<Body> body, ArRef<Animation> animation)
: ArObject(arCW), _body(body), _animation(animation), _time(0.0) {
	_act = Activity::NEW(1.0/25.0);
	_act->setBehavior(thisRef(), &Animator::_animate);
}

Animator::~Animator() {
}

bool Animator::_animate(ArRef<Activity>, double dt) {
	_body->applyPose(_animation->evaluate(_time));

	_time += dt;
	if (_time > _animation->getDuration()) {
		_time = 0.0;
	}
	return true;
}



/*********************************************
 * Ma classe permettant d'animer une lumière *
 *********************************************/

class AnimatedLight3D : public Light3D {
public:
	AR_CLASS(AnimatedLight3D)
	AR_CONSTRUCTOR_2(AnimatedLight3D, ArRef<Base3D>, base, double, radius)
	
protected:
	ArRef<Base3D> _base;
	double _radius;

	ArRef<Activity> _act;
	bool _animate(ArRef<Activity> act, double dt);
};

AR_CLASS_NOVOID_DEF(AnimatedLight3D, Light3D)

AnimatedLight3D::AnimatedLight3D(ArCW & arCW, ArRef<Base3D> base, double radius)
: Light3D(arCW), _base(base), _radius(radius) {
	_act = Activity::NEW(1.0/25.0);
	_act->setBehavior(thisRef(), &AnimatedLight3D::_animate);

	ArRef<Object3D> object = Object3D::NEW();
	object->attachTo(thisRef());
	
	ArRef<Sphere3D> sphere = Sphere3D::NEW();
	sphere->setRadius(0.1 * radius);
	
	ArRef<Shape3D> shape = Shape3D::NEW();
	shape->addRootPart(sphere);
	object->setShape(shape);
}

AnimatedLight3D::~AnimatedLight3D() {
}

bool AnimatedLight3D::_animate(ArRef<Activity> act, double) {
	double x, y, z;
	x = _radius * cos(act->getTime());
	y = _radius * sin(act->getTime());
	z = 0.0;
	_base->localToGlobalPosition(x, y, z);
	setPosition(x, y, z);
	return true;
}

/***********************************
 * Initialisation de la simulation *
 ***********************************/

ArRef<Viewer3D> viewer;
ArRef<SimpleInteractor> interactor;
ArRef<Animator> animator;
ArRef<Light3D> light;

ArRef<Scheduler> simulationInit(void) {
	// Création du scheduler
	ArRef<Scheduler> scd = RealTimeScheduler::NEW(1e-3);


	// Création du viewer
	viewer = Viewer3D::NEW();
	viewer->setCloseAction(Window3D::CLOSE_LEAVE);


	// Positionnement du viewer
	unsigned int w,h;
	Renderer3D::getScreenSize(w,h);
	viewer->setWindowGeometry(0,0, 640,480);
	viewer->setMapped(true);


	// Création de l'interacteur
	interactor = SimpleInteractor::NEW();
	interactor->setRenderer(viewer);


	// Chargement du bonhomme et de son animation
	LoaderData data;
	// ... chargement du bonhomme
	data = HLibLoader::loadFile("AwesomeMan.md5mesh");
	if(data.failed) {
		cerr << "error: Can't find AwesomeMan.md5mesh" << endl;
	}
	ArRef<Body> body = data.body;
	body->accessBodyShape()->setShowSkeleton(false);
	// ... chargement de l'animation
	data = HLibLoader::loadFile("AwesomeMan.md5anim");
	if(data.failed) {
		cerr << "error: Can't find AwesomeMan.md5anim" << endl;
	}
	ArRef<Animation> anim = data.animation;
	body->applyPose(anim->getKeyframe(0));


	// Positionnement de la caméra
	ArRef<BoundingBox3D> bbox = BoundingBox3D::NEW();
	body->accessShape()->readBoundingBox(bbox);
	viewer->setPosition(-2.0*bbox->getMaxSize(), 0.0, 0.0);
	viewer->setFarDistance(50.0*bbox->getMaxSize());


	// Création de la lumière
	/*light = AnimatedLight3D::NEW(body, bbox->getMaxSize());
	light->setSwitch(true);
	light->setDirectional(false);
	light->setColor(1.0,1.0,1.0);*/

	// Chargement des shaders
	// ... de vertex
	ArRef<VertexShader> vSh=VertexShader::NEW();
	if(!vSh->loadURL("AwesomeMan.vp")) {
		cerr << vSh->getErrorMessage() << endl;
	}
	// ... de fragment
	ArRef<FragmentShader> fSh=FragmentShader::NEW();
	if (!fSh->loadURL("AwesomeMan.fp")) {
		cerr << fSh->getErrorMessage() << endl;
	}
	// ... puis création du programme
	ArRef<ShaderProgram> shP=ShaderProgram::NEW();
	if(!shP->setShaders(vSh,fSh)) {
		cerr << shP->getErrorMessage() << endl;
	}

	ArRef<Shape3D> sh = body->accessShape();
	for(unsigned int i=sh->getNbParts();i--;) {
		sh->accessPart(i)->setRenderer(shP);
	}


	// Lancement de l'animation
	animator = Animator::NEW(body, anim);

	return scd;
}




/**************************
 * Le programme principal *
 **************************/

int main(int argc, char **argv) {
	ArSystem arevi(argc, argv);
	hLibInit();
	
	Animator::REGISTER_CLASS();
	AnimatedLight3D::REGISTER_CLASS();
	//CustomShaderProgram::REGISTER_CLASS();
	
	ArSystem::loadPlugin("Imlib2ImageLoader");
	ArSystem::loadPlugin("MagickImageLoader");

	ArSystem::simulationLoop(&simulationInit);
	return 0;
}



